<script lang="ts">
	import { getContext, onDestroy, onMount } from 'svelte'
	import type {
		AppViewerContext,
		BaseAppComponent,
		ComponentCustomCSS,
		ListContext,
		ListInputs,
		RichConfigurations
	} from '../../../types'
	import type { AppInput } from '../../../inputType'
	import RunnableWrapper from '../../helpers/RunnableWrapper.svelte'
	import { writable } from 'svelte/store'
	import {
		createSvelteTable,
		flexRender,
		type HeaderGroup,
		type Row,
		type Table,
		type TableOptions
	} from '@tanstack/svelte-table'
	import AppButton from '../../buttons/AppButton.svelte'
	import { classNames, isObject, sendUserToast } from '$lib/utils'
	import DebouncedInput from '../../helpers/DebouncedInput.svelte'
	import AppTableFooter from './AppTableFooter.svelte'
	import { tableOptions } from './tableOptions'
	import Alert from '$lib/components/common/alert/Alert.svelte'
	import {
		components,
		type ButtonComponent,
		type CheckboxComponent,
		type SelectComponent
	} from '../../../editor/component'
	import { initCss } from '../../../utils'
	import { twMerge } from 'tailwind-merge'
	import { connectOutput, initConfig, initOutput } from '$lib/components/apps/editor/appUtils'
	import ResolveConfig from '../../helpers/ResolveConfig.svelte'
	import AppCheckbox from '../../inputs/AppCheckbox.svelte'
	import AppSelect from '../../inputs/AppSelect.svelte'
	import RowWrapper from '../../layout/RowWrapper.svelte'
	import ResolveStyle from '../../helpers/ResolveStyle.svelte'
	import { Popup } from '$lib/components/common'
	import ComponentOutputViewer from '$lib/components/apps/editor/contextPanel/ComponentOutputViewer.svelte'
	import { EyeIcon, Plug2 } from 'lucide-svelte'
	import AppCell from './AppCell.svelte'
	import sum from 'hash-sum'
	import RefreshButton from '$lib/components/apps/components/helpers/RefreshButton.svelte'

	export let id: string
	export let componentInput: AppInput | undefined
	export let configuration: RichConfigurations
	export let actionButtons: (BaseAppComponent &
		(ButtonComponent | CheckboxComponent | SelectComponent))[]
	export let initializing: boolean | undefined = undefined
	export let customCss: ComponentCustomCSS<'tablecomponent'> | undefined = undefined
	export let render: boolean

	const iterContext = getContext<ListContext>('ListWrapperContext')
	const listInputs: ListInputs | undefined = getContext<ListInputs>('ListInputs')

	type T = Record<string, any>

	let result: Record<string, any>[] | undefined = undefined

	const {
		app,
		worldStore,
		componentControl,
		selectedComponent,
		hoverStore,
		mode,
		connectingInput
	} = getContext<AppViewerContext>('AppViewerContext')

	let searchValue = ''

	let outputs = initOutput($worldStore, id, {
		selectedRowIndex: 0,
		selectedRow: undefined,
		loading: false,
		result: [] as Record<string, any>[],
		inputs: {},
		search: '',
		page: 1
	})

	let inputs = {}
	let loading: boolean = false

	$: setSearch(searchValue)

	function setSearch(srch: string) {
		$table.setPageIndex(0)
		outputs?.search?.set(srch)
	}

	const options = writable<TableOptions<T>>({
		...tableOptions,
		data: [],
		columns: []
	})

	let table = createSvelteTable(options)

	let resolvedConfig = initConfig(
		components['tablecomponent'].initialData.configuration,
		configuration
	)

	let selectedRowIndex = -1

	function toggleRow(row: Record<string, any>, force: boolean = false) {
		let data = { ...row.original }
		let index = data['__index']
		delete data['__index']
		let peak = outputs?.selectedRow.peak()

		if (selectedRowIndex !== index || peak == undefined || sum(data) != sum(peak) || force) {
			selectedRowIndex = index
			outputs?.selectedRow.set(data, force)
			outputs?.selectedRowIndex.set(index, force)
			if (iterContext && listInputs) {
				listInputs.set(id, { selectedRow: data, selectedRowIndex: index })
			}
		}
	}

	onDestroy(() => {
		listInputs?.remove(id)
	})

	let mounted = false
	onMount(() => {
		mounted = true
	})

	$: resolvedConfig?.selectFirstRowByDefault != false &&
		selectedRowIndex === -1 &&
		Array.isArray(result) &&
		result.length > 0 &&
		// We need to wait until the component is mounted so the world is created
		mounted &&
		outputs &&
		toggleRow({ original: { ...result[0], __index: 0 } })

	function searchInResult(result: Array<Record<string, any>>, searchValue: string) {
		if (searchValue === '') {
			return result
		}
		return result.filter((row) => {
			// console.log(Object.values(row).map((value) => value?.toString()))
			return Object.values(row).some((value) => value?.toString()?.includes(searchValue))
		})
	}

	function renderCell(x: any, props: any): any {
		try {
			return flexRender(x, props)
		} catch (e) {
			return undefined
		}
	}

	let filteredResult: Array<Record<string, any>> = []

	function setFilteredResult() {
		console.log('setting filtered result')
		const wIndex = Array.isArray(result)
			? (result as any[]).map((x, i) => ({ ...x, __index: i }))
			: [{ error: 'input was not an array' }]

		if (resolvedConfig.search === 'By Runnable' || resolvedConfig.search === 'Disabled') {
			filteredResult = wIndex ?? []
		} else {
			filteredResult = searchInResult(wIndex ?? [], searchValue)
		}
	}
	$: (result || resolvedConfig.search || searchValue || resolvedConfig.pagination) &&
		setFilteredResult()

	$: outputs.page.set($table.getState().pagination.pageIndex)

	function rerender() {
		if (!Array.isArray(result)) {
			return
		}

		const headers = Array.from(
			new Set(result.flatMap((row) => (typeof row == 'object' ? Object.keys(row ?? {}) : [])))
		)

		$options = {
			...tableOptions,
			...(resolvedConfig?.pagination?.selected != 'manual'
				? {
						initialState: {
							...resolvedConfig.initialState,
							pagination: {
								pageSize: resolvedConfig?.pagination?.configuration?.auto?.pageSize ?? 20
							}
						}
				  }
				: {}),
			manualPagination: resolvedConfig?.pagination?.selected == 'manual',
			pageCount:
				resolvedConfig?.pagination?.selected == 'manual'
					? resolvedConfig?.pagination?.configuration.manual.pageCount ?? -1
					: undefined,
			data: filteredResult,
			columns: headers.map((header) => {
				return {
					accessorKey: header,
					cell: (info) => info.getValue()
				}
			})
		}
		table = createSvelteTable(options)

		if (result) {
			//console.log('rerendering table', result[0])
			if (resolvedConfig?.selectFirstRowByDefault != false) {
				toggleRow({ original: filteredResult[0] }, true)
			}
		}

		if (outputs.page.peak()) {
			$table.setPageIndex(outputs?.page.peak())
		}
	}

	$: filteredResult != undefined && rerender()

	let css = initCss($app.css?.tablecomponent, customCss)

	$componentControl[id] = {
		right: (skipActions: boolean | undefined) => {
			if (skipActions) {
				return false
			}

			const hasActions = actionButtons.length >= 1

			if (hasActions) {
				$selectedComponent = [actionButtons[0].id]
				return true
			}
			return false
		},
		setSelectedIndex: (index: number) => {
			if (filteredResult) {
				toggleRow({ original: filteredResult[index] }, true)
			}
		}
	}

	function getHeaderGroups<T>(table: Table<T>): Array<HeaderGroup<T>> {
		try {
			return table.getHeaderGroups()
		} catch (e) {
			sendUserToast("Couldn't render table header groups: " + e, true)
			console.error(e)
			return []
		}
	}

	function getDisplayNameById(id: string) {
		const component = resolvedConfig?.columnDefs?.find((columnDef) => columnDef.field === id)
		return component?.headerName
	}

	function safeVisibleCell<T>(row: Row<T>) {
		try {
			return row.getVisibleCells()
		} catch (e) {
			sendUserToast("Couldn't render table header groups: " + e, true)
			console.error(e)
			return []
		}
	}

	function updateTable(resolvedConfig, searchValue) {
		if (resolvedConfig?.columnDefs) {
			$table.getAllLeafColumns().map((column) => {
				const columnConfig = resolvedConfig.columnDefs.find(
					// @ts-ignore
					(columnDef) => columnDef.field === column.columnDef.accessorKey
				)

				if (columnConfig?.hideColumn === column.getIsVisible()) {
					column.toggleVisibility()
				}
			})

			$table.setColumnOrder(() =>
				resolvedConfig.columnDefs.map((columnDef: { field: any }) => columnDef.field)
			)
		}
	}

	$: $table && updateTable(resolvedConfig, searchValue)

	function updateCellValue(rowIndex: number, columnIndex: number, newCellValue: string) {
		if (result && rowIndex < result.length) {
			const updatedRow = { ...result[rowIndex] }
			const columnName = $table?.getAllLeafColumns()?.[columnIndex]?.columnDef?.['accessorKey']

			if (!columnName) {
				sendUserToast(`Column name for index ${columnIndex} is not defined.`, true)
				return
			}

			updatedRow[columnName] = newCellValue
			result[rowIndex] = updatedRow
			outputs?.result.set([result])
		}
	}
</script>

{#each Object.keys(components['tablecomponent'].initialData.configuration) as key (key)}
	<ResolveConfig
		{id}
		{key}
		bind:resolvedConfig={resolvedConfig[key]}
		configuration={configuration[key]}
	/>
{/each}

{#each Object.keys(css ?? {}) as key (key)}
	<ResolveStyle
		{id}
		{customCss}
		{key}
		bind:css={css[key]}
		componentStyle={$app.css?.tablecomponent}
	/>
{/each}

<RunnableWrapper
	{outputs}
	{render}
	{componentInput}
	{id}
	bind:initializing
	bind:result
	bind:loading
>
	{#if Array.isArray(result) && result.every(isObject)}
		<div class="flex flex-col h-full">
			{#if resolvedConfig.search !== 'Disabled'}
				<div class="flex flex-row w-full justify-between items-center h-12">
					{#if resolvedConfig.search !== 'Disabled'}
						<div class="grow max-w-[300px]">
							<DebouncedInput
								placeholder="Search..."
								bind:value={searchValue}
								parentClass="h-full "
							/>
						</div>
					{:else}
						<div />
					{/if}

					{#if componentInput?.hideRefreshButton && componentInput['autoRefresh']}
						<RefreshButton {id} {loading} />
					{/if}
				</div>
			{:else if componentInput?.hideRefreshButton && componentInput['autoRefresh']}
				<div class="absolute right-2 top-2 z-50">
					<RefreshButton {id} {loading} />
				</div>
			{/if}
			<div
				class={twMerge(
					'component-wrapper bg-surface',
					'divide-y h-full',
					css?.container?.class ?? '',
					'wm-table-container',
					'flex flex-col'
				)}
				style={css?.container?.style ?? ''}
			>
				<div class="overflow-x-auto flex-1 w-full">
					<table class="relative w-full border-b">
						<thead
							class={twMerge(
								'bg-surface-secondary text-left',
								css?.tableHeader?.class ?? '',
								'wm-table-header',
								'sticky top-0 z-40'
							)}
							style={css?.tableHeader?.style ?? ''}
						>
							{#each getHeaderGroups($table) as headerGroup}
								<tr class="divide-x">
									{#each headerGroup.headers as header}
										{#if header?.column?.columnDef?.header}
											{@const context = header?.getContext()}
											{#if context}
												{@const component = renderCell(header.column.columnDef.header, context)}
												{@const displayName = getDisplayNameById(header.id)}
												<th class="!p-0">
													<div
														class="flex flex-row items-center gap-1 px-4 py-4 text-xs text-primary font-medium border-b"
													>
														{#if displayName}
															{displayName}
														{:else if !header.isPlaceholder && component}
															<svelte:component this={component} />
														{/if}
														{#if header.column.getIsVisible()}
															<button
																class="w-6 flex justify-center items-center"
																on:click={() => {
																	header.column.toggleVisibility()
																}}
															>
																<EyeIcon
																	size="14"
																	class="hover:text-gray-600 text-gray-400 rounded-full "
																/>
															</button>
														{/if}
													</div>
												</th>
											{/if}
										{/if}
									{/each}
									{#if actionButtons.length > 0}
										<th class="!p-0">
											<div
												class="flex flex-row items-center px-4 py-4 text-xs text-primary font-medium border-b"
											>
												Actions
											</div>
										</th>
									{/if}
								</tr>
							{/each}
						</thead>
						<tbody
							class={twMerge('divide-y bg-surface', css?.tableBody?.class ?? '', 'wm-table-body')}
							style={css?.tableBody?.style ?? ''}
						>
							{#each $table.getRowModel().rows as row, index (row.id)}
								{@const isLastRow = index === filteredResult.length - 1}
								{@const rowIndex = row.original['__index']}
								<tr
									class={twMerge(
										isLastRow ? '!border-b-0' : '',
										'divide-x w-full',
										index % 2 === 0 ? 'bg-gray-50/50' : '',
										selectedRowIndex === rowIndex
											? 'bg-blue-100 hover:bg-blue-200 dark:bg-surface-selected dark:hover:bg-surface-hover divide-blue-200 hover:divide-blue-300 dark:divide-gray-600 dark:hover:divide-gray-700 wm-table-row-selected'
											: 'hover:bg-blue-50 dark:hover:bg-surface-hover wm-table-row'
									)}
								>
									{#each safeVisibleCell(row) as cell, index (index)}
										{#if cell?.column?.columnDef?.cell}
											{@const context = cell?.getContext()}
											{#if context}
												<AppCell
													on:keydown={() => toggleRow(row)}
													on:click={() => toggleRow(row)}
													type={resolvedConfig.columnDefs?.find(
														// TS types are wrong here
														// @ts-ignore
														(c) => c.field === cell.column.columnDef.accessorKey
													)?.type ?? 'text'}
													value={cell.getValue()}
													width={cell.column.getSize()}
													on:update={(event) => {
														updateCellValue(rowIndex, index, event.detail.value)
													}}
												/>
											{/if}
										{/if}
									{/each}

									{#if actionButtons.length > 0}
										<td
											class="p-2"
											on:keypress={() => toggleRow(row)}
											on:click={() => toggleRow(row)}
											style="width: {(actionButtons ?? []).length * 130}px"
										>
											<div class="center-center h-full w-full flex-wrap gap-2">
												{#each actionButtons as actionButton, actionIndex (actionButton?.id)}
													<!-- svelte-ignore a11y-no-static-element-interactions -->
													<RowWrapper
														value={row.original}
														index={rowIndex}
														onSet={(id, value) => {
															if (!inputs[id]) {
																inputs[id] = { [rowIndex]: value }
															} else {
																inputs[id] = { ...inputs[id], [rowIndex]: value }
															}

															outputs?.inputs.set(inputs, true)
														}}
														onRemove={(id) => {
															if (inputs?.[id] == undefined) {
																return
															}
															delete inputs[id][rowIndex]
															inputs[id] = { ...inputs[id] }
															if (Object.keys(inputs?.[id] ?? {}).length == 0) {
																delete inputs[id]
																inputs = { ...inputs }
															}
															outputs?.inputs.set(inputs, true)
														}}
													>
														<!-- svelte-ignore a11y-mouse-events-have-key-events -->
														<div
															on:mouseover|stopPropagation={() => {
																if (actionButton.id !== $hoverStore) {
																	$hoverStore = actionButton.id
																}
															}}
															on:mouseout|stopPropagation={() => {
																if ($hoverStore !== undefined) {
																	$hoverStore = undefined
																}
															}}
															class={classNames(
																($selectedComponent?.includes(actionButton.id) ||
																	$hoverStore === actionButton.id) &&
																	$mode !== 'preview'
																	? 'outline outline-indigo-500 outline-1 outline-offset-1 relative'
																	: 'relative'
															)}
														>
															{#if $mode !== 'preview'}
																<!-- svelte-ignore a11y-click-events-have-key-events -->
																<!-- svelte-ignore a11y-no-static-element-interactions -->
																<span
																	title={`Id: ${actionButton.id}`}
																	class={classNames(
																		'px-2 text-2xs font-bold w-fit absolute shadow  -top-2 -left-2 border z-50 rounded-sm',
																		'bg-indigo-500/90 border-indigo-600 text-white',
																		$selectedComponent?.includes(actionButton.id) ||
																			$hoverStore === actionButton.id
																			? 'opacity-100'
																			: 'opacity-0'
																	)}
																	on:click|stopPropagation={() => {
																		$selectedComponent = [actionButton.id]
																	}}
																>
																	{actionButton.id}
																</span>

																{#if $connectingInput.opened}
																	<div class="absolute z-50 left-8 -top-[10px]">
																		<Popup
																			floatingConfig={{
																				strategy: 'absolute',
																				placement: 'bottom-start'
																			}}
																		>
																			<svelte:fragment slot="button">
																				<button
																					class="bg-red-500/70 border border-red-600 px-1 py-0.5"
																					title="Outputs"
																					aria-label="Open output"><Plug2 size={12} /></button
																				>
																			</svelte:fragment>
																			<ComponentOutputViewer
																				suffix="table"
																				on:select={({ detail }) =>
																					connectOutput(
																						connectingInput,
																						'buttoncomponent',
																						actionButton.id,
																						detail
																					)}
																				componentId={actionButton.id}
																			/>
																		</Popup>
																	</div>
																{/if}
															{/if}
															{#if rowIndex == 0}
																{@const controls = {
																	left: () => {
																		if (actionIndex === 0) {
																			$selectedComponent = [id]
																			return true
																		} else if (actionIndex > 0) {
																			$selectedComponent = [actionButtons[actionIndex - 1].id]
																			return true
																		}
																		return false
																	},
																	right: () => {
																		if (actionIndex === actionButtons.length - 1) {
																			return id
																		} else if (actionIndex < actionButtons.length - 1) {
																			$selectedComponent = [actionButtons[actionIndex + 1].id]
																			return true
																		}
																		return false
																	}
																}}
																{#if actionButton.type == 'buttoncomponent'}
																	<AppButton
																		noInitialize
																		extraKey={'idx' + rowIndex}
																		{render}
																		noWFull
																		preclickAction={async () => {
																			toggleRow(row)
																		}}
																		id={actionButton.id}
																		customCss={actionButton.customCss}
																		configuration={actionButton.configuration}
																		recomputeIds={actionButton.recomputeIds}
																		extraQueryParams={{ row: row.original }}
																		componentInput={actionButton.componentInput}
																		{controls}
																	/>
																{:else if actionButton.type == 'checkboxcomponent'}
																	<AppCheckbox
																		noInitialize
																		extraKey={'idx' + rowIndex}
																		{render}
																		id={actionButton.id}
																		customCss={actionButton.customCss}
																		configuration={actionButton.configuration}
																		recomputeIds={actionButton.recomputeIds}
																		onToggle={actionButton.onToggle}
																		preclickAction={async () => {
																			toggleRow(row)
																		}}
																		{controls}
																	/>
																{:else if actionButton.type == 'selectcomponent'}
																	<div class="w-40">
																		<AppSelect
																			noDefault
																			noInitialize
																			extraKey={'idx' + rowIndex}
																			{render}
																			id={actionButton.id}
																			customCss={actionButton.customCss}
																			configuration={actionButton.configuration}
																			recomputeIds={actionButton.recomputeIds}
																			onSelect={actionButton.onSelect}
																			preclickAction={async () => {
																				toggleRow(row)
																			}}
																			{controls}
																		/>
																	</div>
																{/if}
															{:else if actionButton.type == 'buttoncomponent'}
																<AppButton
																	noInitialize
																	extraKey={'idx' + rowIndex}
																	{render}
																	noWFull
																	id={actionButton.id}
																	customCss={actionButton.customCss}
																	configuration={actionButton.configuration}
																	recomputeIds={actionButton.recomputeIds}
																	preclickAction={async () => {
																		toggleRow(row)
																	}}
																	extraQueryParams={{ row: row.original }}
																	componentInput={actionButton.componentInput}
																/>
															{:else if actionButton.type == 'checkboxcomponent'}
																<AppCheckbox
																	noInitialize
																	extraKey={'idx' + rowIndex}
																	{render}
																	id={actionButton.id}
																	customCss={actionButton.customCss}
																	configuration={actionButton.configuration}
																	recomputeIds={actionButton.recomputeIds}
																	onToggle={actionButton.onToggle}
																	preclickAction={async () => {
																		toggleRow(row)
																	}}
																/>
															{:else if actionButton.type == 'selectcomponent'}
																<div class="w-40">
																	<AppSelect
																		noDefault
																		noInitialize
																		extraKey={'idx' + rowIndex}
																		{render}
																		id={actionButton.id}
																		customCss={actionButton.customCss}
																		configuration={actionButton.configuration}
																		recomputeIds={actionButton.recomputeIds}
																		onSelect={actionButton.onSelect}
																		preclickAction={async () => {
																			toggleRow(row)
																		}}
																	/>
																</div>
															{/if}
														</div>
													</RowWrapper>
												{/each}
											</div>
										</td>
									{/if}
								</tr>
							{/each}
						</tbody>
					</table>
				</div>

				<AppTableFooter
					download={resolvedConfig?.downloadButton}
					pageSize={resolvedConfig?.pagination?.configuration?.auto?.pageSize ?? 20}
					manualPagination={resolvedConfig?.pagination?.selected == 'manual'}
					result={filteredResult}
					{table}
					class={twMerge(css?.tableFooter?.class, 'wm-table-footer')}
					style={css?.tableFooter?.style}
					{loading}
				/>
			</div>
		</div>
	{:else if result != undefined}
		<div class="flex flex-col h-full w-full overflow-auto">
			<Alert title="Parsing issues" type="error" size="xs" class="h-full w-full ">
				The result should be an array of objects. Received:
				<pre class="w-full bg-surface p-2 rounded-md whitespace-pre-wrap mt-2"
					>{JSON.stringify(result, null, 4)}
				</pre>
			</Alert>
		</div>
	{/if}
</RunnableWrapper>
